package data;

import java.util.*;
import java.sql.*;

public class CellSampleFactory extends DataFactory
{
	public CellSampleFactory( Database db )
	{
		super( "CellSamples","cellsamples_id_seq", db );
	}
	
	protected DataElement instantiate()
	{
		return new CellSample( this );
	}
	
	
	
	// typed convenience functions
	public CellSample findOne( String conditions )
	{
		return (CellSample) _findOne( conditions );
	}
	public CellSample create()
	{
		return (CellSample) _create();
	}
	public CellSample lookup( int id )
	{
		return (CellSample) _lookup( id );
	}

    public LinkedList findInRange( double latitude,
				   double longitude,
				   java.util.GregorianCalendar time,
				   double latitudeRange,
				   double longitudeRange,
				   int minutesRange,
				   int precisionDigits,
				   double axialTilt, 
				   double eccentricity,
				   String conditions )
    {
        java.util.GregorianCalendar gcMinTime = (java.util.GregorianCalendar) time.clone();
        java.util.GregorianCalendar gcMaxTime = (java.util.GregorianCalendar) time.clone();

        gcMinTime.add( java.util.Calendar.MINUTE, -minutesRange );
        gcMaxTime.add( java.util.Calendar.MINUTE,  minutesRange );

        String strTargetTime = "'" + ( new java.sql.Timestamp( time.getTime().getTime() ) ).toString() + "'";
        String strMinTime    = "'" + ( new java.sql.Timestamp( gcMinTime.getTime().getTime() ) ).toString() + "'";
        String strMaxTime    = "'" + ( new java.sql.Timestamp( gcMaxTime.getTime().getTime() ) ).toString() + "'";

        // TODO: handle lat/long wrapping

        String conditions2 = "abs( cs.coord_longitude - " + longitude + ") < " + longitudeRange + " AND " +
            " abs( cs.coord_latitude - " + latitude + ") < " + latitudeRange + " AND " +
            " cs.time >= " + strMinTime + " AND cs.time <= " + strMaxTime;

	// grid specific stuff
    double epsilon = 0.0001;
	conditions2 = conditions2 + 
		" AND g.param_precision_digits >= " + precisionDigits + 
		" AND phys_axial_tilt > " + (axialTilt - epsilon) +
		" AND phys_axial_tilt < " + (axialTilt + epsilon) +
		" AND phys_orbital_eccentricity > " + (eccentricity - epsilon) +
		" AND phys_orbital_eccentricity < " + (eccentricity + epsilon);

        if( conditions != null && conditions.length() > 0 ) {
            conditions2 = conditions2 + " AND (" + conditions + ")";
        }

	String sql = "SELECT cs.* FROM " + getTableName() + " AS cs JOIN grids AS g ON cs.grid_id = g.id WHERE " + conditions2;

	System.out.println( sql );

	ResultSet rs = getDatabase().execQuery( sql );
	return listFromResultSet( rs );

    }

    public InterpSample findMinError( double latitude, double longitude, GregorianCalendar time, boolean above, boolean left, boolean before, double timeTolerance, double geoTolerance, int precisionDigits, double axialTilt, double eccentricity )
    {
    	double epsilon = 0.0001;
    	String sqlTime = "'" + ( new java.sql.Timestamp( time.getTime().getTime() ) ).toString() + "'";

    	double timeScale = 1.0 / (timeTolerance * 60.0 * 1000.0);
    	double geoScale = 1.0 / Math.sqrt( 2.0 * geoTolerance * geoTolerance );
    	long epoch = time.getTime().getTime();

    	InterpSample is = null;

    	/* First see if we can find some good grids */
    	String sql = "SELECT g.* FROM grids AS g WHERE param_precision_digits >= " + precisionDigits +
    	" AND phys_axial_tilt > " + (axialTilt - epsilon) +
    	" AND phys_axial_tilt < " + (axialTilt + epsilon) +
    	" AND phys_orbital_eccentricity > " + (eccentricity - epsilon) +
    	" AND phys_orbital_eccentricity < " + (eccentricity + epsilon);

    	ResultSet rs = getDatabase().execQuery(sql);
    	try {
    		if(!rs.next()) {
    			return null; // No grid matches so we are a bit screwed
    		} else {
    			do {
    				Grid curGrid = (Grid)getDatabase().getGridFactory().build(rs);
    				// Now we can check the time
    				String error = "( " + timeScale + " * ( EXTRACT( EPOCH FROM gs.time ) - " + epoch + " ) )";
    				sql = "SELECT gs.*, " + error + "AS error FROM gridsamples AS gs WHERE " +
    				" gs.time " + ( before ? "<" : ">" ) + " " + sqlTime +
    				" AND gs.grid_id = " + curGrid.getID() +
    				" ORDER BY error ASC LIMIT 1";
    				
    				ResultSet rs2 = getDatabase().execQuery(sql);
    				try {
    					if(!rs2.next())
    						continue; // No valid sample here
    					GridSample curSample = (GridSample)getDatabase().getGridSampleFactory().build(rs2);
    					// Here we try to get an actual cell
    					error = "( " + geoScale + " * sqrt( (cs.coord_latitude - " + latitude + " ) ^ 2 + (cs.coord_longitude - " + longitude + " ) ^ 2 ) )";

    					sql = "SELECT cs.*, " + error + "  AS error FROM CellSamples AS cs WHERE "+
    					" cs.gridsample_id = " + curSample.getID() + 
    					" AND cs.coord_longitude " + ( left ? "<" : ">" ) + " " + longitude +
    					" AND cs.coord_latitude " + ( above ? ">" : "<" ) + " " + latitude +
    					" ORDER BY error ASC LIMIT 1";

    					ResultSet rs3 = getDatabase().execQuery( sql );

    					try {
    						if( !rs3.next() ) {
    							continue; // No result
    						} else {
    							CellSample cs = (CellSample) instantiate();
    							cs.loadFrom( rs3 );
    							double err = rs3.getDouble( "error" ) + rs2.getDouble("error");

    							if(is != null) {
    								if(1.0 / err > is.weight) {
    									is.weight = 1.0 / err;
    									is.sample = cs;
    								}
    							} else {
    								is = new InterpSample();
    								is.weight = 1.0 / err;
    								is.sample = cs;
    							}
    						}
    					} catch( SQLException sqle ) {
    						return null;
    					}
    				} catch( SQLException sqle ) {
    					return null;
    				}
    			} while(rs.next());
    		}
    	} catch( SQLException sqle ) {
    		return null;
    	}

    	return is;
    }

}
